﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace DDD {


    /// <summary>
    /// サンプラー
    /// </summary>
    /// <remarks>
    /// 旧名：テクスチャー
    /// </remarks>
    public class Sampler : Object {

        #region Field
        SamplerType type;
        Image2D[] images;
        LevelFilter levelFilter;
        ImageFilter imageFilter;
        TextureWrapMode wrapMode;
        #endregion

        #region Constructor
        /// <summary>
        /// mutableなTextureオブジェクトを作成するコンストラクター
        /// </summary>
        /// <remarks>
        /// </remarks>
        /// <param name="type">テクスチャータイプ</param>
        public Sampler (SamplerType type) {
            this.type = type;
            this.images = new Image2D[type.ToImageCount ()];
            this.levelFilter = LevelFilter.Linear;
            this.imageFilter = ImageFilter.Linear;
            this.wrapMode = TextureWrapMode.Repeat;
        }
        #endregion

        #region Property

        /// <summary>
        /// サンプラータイプ
        /// </summary>
        public SamplerType Type {
            get { return type; }
        }

        /// <summary>
        /// 画像枚数
        /// </summary>
        /// <remarks>
        /// このテクスチャーで使用する画像の枚数を返します。
        /// 2Dテクスチャーなら1枚、キューブテクスチャーなら6枚です。
        /// </remarks>
        public int ImageCount {
            get { return images.Length; }
        }

        /// <summary>
        /// テクスチャー画像
        /// </summary>
        public Image2D Image {
            get { return images[0]; }
        }

        /// <summary>
        /// テクスチャー画像を列挙する列挙子
        /// </summary>
        /// <remarks>
        /// テクスチャーとして表示する画像を全て列挙する列挙子です。
        /// nullは含まれません。
        /// </remarks>
        public IEnumerable<Image2D> Images {
            get { return images.Where (x => x != null); }
        }

        /// <summary>
        /// ミップマップ階層フィルター
        /// </summary>
        /// <remarks>
        /// ミップマップ階層間の画像の補完方法を指定します。
        /// </remarks>
        public LevelFilter LevelFilter {
            get { return levelFilter; }
            private set { this.levelFilter = value; }
        }

        /// <summary>
        /// 画像フィルター
        /// </summary>
        /// <remarks>
        /// 1枚の画像内の補完方法の指定です。
        /// </remarks>
        public ImageFilter ImageFilter {
            get { return imageFilter; }
            private set { this.imageFilter = value; }
        }

        /// <summary>
        /// 繰り返しモード
        /// </summary>
        /// <remarks>
        /// テクスチャー座標がの範囲外[0,1)の時の解決方法です。
        /// </remarks>
        public TextureWrapMode WrapMode {
            get { return wrapMode; }
            private set { this.wrapMode = value; }
        }

        /// <summary>
        /// このオブジェクトが参照する全DDDオブジェクトを列挙する列挙子
        /// </summary>
        /// <remarks>
        /// 参照にnullは含まれません。同一のオブジェクトが複数出現する可能性があります。
        /// </remarks>
        public override IEnumerable<Object> References {
            get {
                var objs = base.References.ToList ();
                objs.AddRange (images);
                objs.RemoveAll (x => x == null);
                return objs;
            }
        }


        #endregion

        #region Method

        /// <summary>
        /// 指定の面の画像を取得します
        /// </summary>
        /// <param name="face">面方位(インデックス)</param>
        /// <returns>画像</returns>
        public Image2D GetImage (CubeFace face) {
            var index = (int)face;
            if (index < 0 || index > ImageCount) {
                throw new IndexOutOfRangeException ("Index is out of ragne");
            }
            return images[index];
        }

        /// <summary>
        /// 画像をセットします
        /// </summary>
        /// <remarks>
        /// このメソッドは<see cref="SamplerType.Texture2D"/>タイプのサンプラーに使用します。
        /// (*1) 整数フォーマットの時は強制的にフィルターがBase(ミップマップ無効)に設定される。
        /// OpenGLの仕様書には書かれていないが少なくともNVIDIAのGT640で試した限り
        /// ミップマップを無効にしないと値が得られなかった(textureLodでlod=0にしても駄目)。
        /// </remarks>
        /// <param name="image">画像</param>
        public void SetImage (Image2D image) {
            this.images[0] = image;

            if (image != null && image.PixelFormat.ToCategory () == PixelFormatCategory.Integer) {
                this.imageFilter = ImageFilter.Nearest;
                this.levelFilter = LevelFilter.Base;       // (*1)
            }

        }

        /// <summary>
        /// 指定の面方位に画像をセットします
        /// </summary>
        /// <remarks>
        /// このメソッドは<see cref="SamplerType.TextureCube"/>タイプのサンプラーに使用します。
        /// </remarks>
        /// <param name="face">面方位</param>
        /// <param name="image">画像</param>
        public void SetImage (CubeFace face, Image2D image) {
            if (face < 0 || (int)face >= ImageCount) {
                throw new IndexOutOfRangeException ("Face is out of range");
            }
            this.images[(int)face] = image;
        }

        /// <summary>
        /// 画像を一括してセットします
        /// </summary>
        /// <remarks>
        /// 必要な画像枚数は<see cref="ImageCount"/>で取得可能です。
        /// </remarks>
        /// <param name="images">画像の配列</param>
        public void SetImages (Image2D[] images) {
            if (images.Length < ImageCount) {
                throw new ArgumentException ("Images is too short");
            }

            Array.Copy (images, this.images, ImageCount);
        }

        /// <summary>
        /// フィルター関数を設定します
        /// </summary>
        /// <remarks>
        /// 整数フォーマットの画像をサンプリングする時はレベルフィルターは必ずBaseで無ければならない。
        /// SetImage()の時に自動で変更されているはずなので手動で変更するのは禁止。
        /// </remarks>
        /// <param name="imageFilter">1枚の画像内フィルター</param>
        /// <param name="levelFilter">ミップマップ階層間フィルター</param>
        public void SetFilter (ImageFilter imageFilter, LevelFilter levelFilter) {
            if (images[0] != null &&
                images[0].PixelFormat.ToCategory () == PixelFormatCategory.Integer &&
                levelFilter != LevelFilter.Base) {
                throw new InvalidOperationException ("Can't change level filter of Integer format image");
            }
            this.imageFilter = imageFilter;
            this.levelFilter = levelFilter;
        }

        /// <summary>
        /// 繰り返しモードを設定します
        /// </summary>
        /// <param name="mode">繰り返しモード</param>
        public void SetWrapMode (TextureWrapMode mode) {
            this.wrapMode = mode;
        }


        /// <summary>
        /// 現在のオブジェクトの状態を表す文字列を返します。
        /// </summary>
        /// <returns>現在のオブジェクトを説明する文字列</returns>
        public override string ToString () {
            return string.Format ("Texture: Type={3}, RepeatMode={0}, ImageFilter={1}, LevelFilter={2}", WrapMode, ImageFilter, LevelFilter, Type);
        }
        #endregion


    }
}
